package fractal.fractal;

import fractal.FractalFrame;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class FractalData implements Cloneable
{
	static int recordW = FractalFrame.MW, recordH = FractalFrame.MH;
	protected int W, H, maxN;
	protected double xmin, ymin, dxy, R, angle, rotateX, rotateY, sff = 1;

	public FractalData(int w, int h, int maxN, double xmin, double ymin,
			double dxy, double R, double angle, double x, double y, double f)
	{
		this.W = w;
		this.H = h;
		this.maxN = maxN;
		this.xmin = xmin;
		this.ymin = ymin;
		this.dxy = dxy;
		this.R = R;
		this.angle = angle;
		this.rotateX = x;
		this.rotateY = y;
		this.sff = f;
	}

	public FractalData()
	{
	}

	@Override
	public FractalData clone()
	{
		try
		{
			return (FractalData) super.clone();
		} catch (CloneNotSupportedException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public void setRecordData(Fractal fr)
	{
		this.W = fr.width;
		this.H = fr.height;
		this.maxN = fr.maxN;
		this.xmin = fr.xmin;
		this.ymin = fr.ymin;
		this.dxy = fr.dxy;
		this.R = fr.R;
		this.angle = fr.angle;
		this.rotateX = fr.rotateX;
		this.rotateY = fr.rotateY;
		this.sff = fr.sff;
		toRecordData();
	}

	public void setRecordData(FractalData fd)
	{
		this.W = fd.W;
		this.H = fd.H;
		this.maxN = fd.maxN;
		this.xmin = fd.xmin;
		this.ymin = fd.ymin;
		this.dxy = fd.dxy;
		this.R = fd.R;
		this.angle = fd.angle;
		this.rotateX = fd.rotateX;
		this.rotateY = fd.rotateY;
		this.sff = fd.sff;
		toRecordData();
	}

	public void setData(double xmin, double ymin, double dxy)
	{
		this.xmin = xmin;
		this.ymin = ymin;
		this.dxy = dxy;
	}

	public double getDxy()
	{
		return dxy;
	}

	public FractalData expandSize(int newW, int newH)
	{
		double dd, dh, ww = newW, hh = newH;
		if (ww / W >= hh / H)
		{
			dd = (W * dxy) / ww;
			dh = hh * W / ww;
			ymin = ymin + (H - dh) / 2 * dxy;
		} else
		{
			dd = (H * dxy) / hh;
			dh = ww * H / hh;
			xmin = xmin + (W - dh) / 2 * dxy;
		}
		dxy = dd;
		W = newW;
		H = newH;
		return this;
	}

	public FractalData toRecordData()
	{
		if (W == recordW && H == recordH)
			return this;
		double dd, ww = W, hh = H;
		if (ww / recordW >= hh / recordH)
		{
			dd = (recordW / ww * hh);
			dxy = ww * dxy / recordW;
			ymin = ymin - (recordH - dd) * dxy / 2;
		} else
		{
			dd = (recordH / hh * ww);
			dxy = hh * dxy / recordH;
			xmin = xmin - (recordW - dd) * dxy / 2;
		}
		W = recordW;
		H = recordH;
		return this;
	}

	public static ArrayList<FractalData> getDatas(FractalData fd1,
			FractalData fd2, int frame)
	{
		if (fd1.dxy <= fd2.dxy)
			return calculateDatas(fd1, fd2, frame);
		else
		{
			ArrayList<FractalData> datas = calculateDatas(fd2, fd1, frame);
			FractalData tData;
			int size = datas.size();
			int half = size / 2;
			for (int i = 0, j = size - 1; i < half; i++, j--)
			{
				tData = datas.get(i);
				datas.set(i, datas.get(j));
				datas.set(j, tData);
			}
			return datas;
		}
	}

	private static ArrayList<FractalData> calculateDatas(FractalData fd1,
			FractalData fd2, int frame)
	{
		ArrayList<FractalData> datas = new ArrayList<>();
		FractalData fd = fd1.clone();
		double yan, xan, q, temp, temp2, ang, dsf, roX, roY, dr, dn;
		temp = fd2.dxy / fd1.dxy;
		temp2 = fd2.xmin - fd1.xmin;
		ang = (fd2.angle - fd1.angle) / frame;
		dsf = (fd2.sff - fd1.sff) / frame;
		dr = (fd2.R - fd1.R) / frame;
		dn = (fd2.maxN - fd1.maxN) / (double) frame;
		// System.out.println(dn);
		if (temp != 1 && temp2 != 0)
		{
			q = Math.pow(temp, 1.0 / frame);
			xan = temp2 * (1 - q) / (1 - temp);
			yan = (fd2.ymin - fd1.ymin) * xan / temp2;
			roX = (fd2.rotateX - fd1.rotateX) * xan / temp2;
			roY = (fd2.rotateY - fd1.rotateY) * xan / temp2;
			for (int i = 1; i <= frame; i++)
			{
				fd.xmin += xan;
				fd.ymin += yan;
				fd.dxy *= q;
				fd.angle += ang;
				fd.sff += dsf;
				fd.rotateX += roX;
				fd.rotateY += roY;
				fd.R += dr;
				fd.maxN = (int) (fd1.maxN + dn * i);
				// System.out.println(fd.maxN);
				datas.add(fd.clone());
				xan *= q;
				yan *= q;
				roX *= q;
				roY *= q;
			}
		} else
		{
			xan = temp2 / frame;
			yan = (fd2.ymin - fd1.ymin) / frame;
			roX = (fd2.rotateX - fd1.rotateX) / frame;
			roY = (fd2.rotateY - fd1.rotateY) / frame;
			for (int i = 1; i <= frame; i++)
			{
				fd.xmin += xan;
				fd.ymin += yan;
				fd.angle += ang;
				fd.sff += dsf;
				fd.rotateX += roX;
				fd.rotateY += roY;
				fd.R += dr;
				fd.maxN += dn;
				datas.add(fd.clone());
			}
		}
		datas.add(fd2.clone());
		return datas;
	}

	public void writeToDataStream(DataOutputStream dos) throws IOException
	{
		dos.writeInt(W);
		dos.writeInt(H);
		dos.writeInt(maxN);
		dos.writeDouble(xmin);
		dos.writeDouble(ymin);
		dos.writeDouble(dxy);
		dos.writeDouble(R);
		dos.writeDouble(angle);
		dos.writeDouble(rotateX);
		dos.writeDouble(rotateY);
		dos.writeDouble(sff);
	}

	public void readFromDataStream(DataInputStream dis, int version)
			throws IOException
	{
		if (version >= 6)
		{
			W = dis.readInt();
			H = dis.readInt();
		} else
		{
			W = FractalFrame.MW;
			H = FractalFrame.MH;
		}
		maxN = dis.readInt();
		xmin = dis.readDouble();
		ymin = dis.readDouble();
		dxy = dis.readDouble();
		R = dis.readDouble();
		angle = dis.readDouble();
		rotateX = dis.readDouble();
		rotateY = dis.readDouble();
		if (version >= 6)
			sff = dis.readDouble();
		else
			sff = 1;
	}
}
